import operator
import os
import random
# 修复：添加 time 库用于短暂暂停，优化体验
import time
from typing import List, Dict, TypedDict, Annotated

from dotenv import load_dotenv
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END

# 加载环境变量
load_dotenv()

# ---------------- 配置区域 ----------------
# 建议使用 GPT-4o，因为它的逻辑推理和防作弊判定能力比 3.5 强很多
# 请确保你的 .env 文件中配置了正确的环境变量
try:
    llm = ChatOpenAI(
        model=os.getenv("LONGMAO_MODEL"),
        base_url=os.getenv("LONGMAO_BASE_URL"),
        api_key=os.getenv("LONGMAO_API_KEY"),
        temperature=0.7
    )
except Exception as e:
    print(f"Error initializing LLM: {e}")
    print("Please check your .env file environment variables.")
    exit(1)

# 真人玩家的名字
HUMAN_NAME = "我(真人)"


# ---------------- 1. 定义游戏状态 ----------------
class GameState(TypedDict):
    # --- 基础信息 ---
    players: List[str]  # 玩家列表
    player_words: Dict[str, str]  # 每个人的词
    spy_player: str  # 谁是卧底

    # --- 游戏进程 ---
    history: Annotated[List[str], operator.add]  # 聊天记录
    current_turn_idx: int  # 当前轮到谁发言的索引
    round_count: int  # 第几轮辩论

    # --- 状态标志 ---
    votes: Dict[str, str]  # 投票箱
    game_result: str  # 游戏结果文本 (有值则游戏结束)
    cheater_detected: str  # 如果有人撒谎作弊，记录名字


# ---------------- 2. 核心节点逻辑 ----------------

def init_game_node(state: GameState):
    """上帝节点：发牌"""
    print("\n" + "=" * 40)
    print("🎮 游戏开始！正在分配词汇...")

    # 词库：(平民词, 卧底词)
    word_pairs = [
        ("苹果", "梨"), ("微信", "QQ"), ("麦当劳", "肯德基"),
        ("成吉思汗", "努尔哈赤"), ("蜘蛛侠", "蝙蝠侠"), ("程序员", "黑客")
    ]
    common_word, spy_word = random.choice(word_pairs)

    # 定义玩家 (1个真人 + 2个AI)
    players = [HUMAN_NAME, "AI_1号", "AI_2号"]
    # 打乱顺序，这样你就不知道自己是几号位
    random.shuffle(players)

    spy = random.choice(players)

    allocation = {}
    for p in players:
        allocation[p] = spy_word if p == spy else common_word

    # 在控制台悄悄告诉真人他的词
    human_word = allocation[HUMAN_NAME]
    identity = '卧底' if spy == HUMAN_NAME else '平民'
    print(f"🤫 嘘！你的身份是【{identity}】")
    print(f"🔑 你的词是：【{human_word}】 (切记：描述时不能直接包含这个词！)")
    print("-" * 40)
    print(f"👥 玩家发言顺序：{' -> '.join(players)}")
    print("=" * 40 + "\n")
    time.sleep(1)

    return {
        "players": players,
        "player_words": allocation,
        "spy_player": spy,
        "history": [],
        "current_turn_idx": 0,
        "round_count": 1,
        "votes": {},
        "cheater_detected": None
    }


def speaker_node(state: GameState):
    """发言节点：处理真人输入 或 AI生成（集成高级策略）"""
    current_player = state["players"][state["current_turn_idx"]]
    my_word = state["player_words"][current_player]
    spy_player = state["spy_player"]
    round_num = state["round_count"]
    history_str = "\n".join(state['history']) if state['history'] else "（暂无，你是首个发言）"

    print(f"👉 轮到 [{current_player}] 发言...")

    # === 分支 A: 真人玩家 (保持不变，含本地检查) ===
    if current_player == HUMAN_NAME:
        print(f"   (提示: 你的词是【{my_word}】，不能直接说出来哦)")
        if len(state['history']) > 0:
            print(f"   近期发言: {state['history'][-2:]}")

        while True:
            content = input(f"🎤 请输入你的描述 > ").strip()
            if not content:
                print("⚠️ 描述不能为空。")
                continue
            if my_word in content:
                print(f"🚫 犯规警告！不能直接包含关键词【{my_word}】。请重试。")
                continue
            break
        msg = f"[{current_player}]: {content}"

    # === 分支 B: AI 玩家 (核心改造区) ===
    else:
        # 判断 AI 的真实身份，加载不同的策略 Prompt
        is_spy = (current_player == spy_player)

        if is_spy:
            # ================= 🕵️‍♂️ 卧底专属策略 Prompt (升级版 2.0) =================
            prompt = f"""
                        你正在玩《谁是卧底》。
                        **你的绝密身份是：卧底！**
                        你的词是【{my_word}】，而绝大多数人的词是另一个（你不知道是什么）。
                        你的目标是：隐藏自己，混入人群，绝对不能被投票出局。
                        现在是第 {round_num} 轮。

                        历史发言：
                        {history_str}

                        请严格执行以下思维链步骤，然后输出最终描述：

                        ### 第一步：风险评估 (Inner Monologue)
                        1. **我是不是第一个发言？** 如果是，风险极高！你必须选择一个极其安全、模糊的描述，因为你没有任何信息参考。
                        2. **如果不是第一个发言**：分析前面人的描述。寻找一个【{my_word}】的特征，这个特征必须是宽泛的，有很大可能性也适用于其他同类事物。

                        ### 第二步：制定伪装策略 (关键！)
                        - **绝对规则**：必须描述【{my_word}】的真实特征，严禁撒谎。
                        - **生存法则（核心）**：
                        
                            - **禁止使用排他性、唯一性的精确特征！**（例如：如果你的词是“努尔哈赤”，绝对不能说“清朝奠基者”这种一下子就定死的描述。）
                            - **选择上位概念或模糊属性**：使用更宽泛的类别、性质或感受。
                            - **例子**：
                                - 如果词是“努尔哈赤”（人物），不要说具体的朝代成就，可以说“他是一位古代的军事领袖”、“他在历史上很有名”、“他创立了一番事业”。（这些描述对于成吉思汗、刘邦、朱元璋都成立，这才是好的伪装）。
                                - 如果词是“梨”，不要说“形状上小下大”，可以说“这是一种常见的水果”、“它吃起来有甜味”。

                        ### 第三步：最终输出检查
                        - 在心中默念你的描述：这句话是不是太具体了？是不是只适用于【{my_word}】？如果是，立刻换一个更模糊的。
                        - 最终只输出一句话描述，不要暴露思维过程。
                        """
        else:
            # ================= 🧑‍🤝‍🧑 平民专属策略 Prompt =================
            prompt = f"""
            你正在玩《谁是卧底》。
            **你的身份是：平民。**
            你的词是【{my_word}】。场上有一个人的词和大家不一样。
            你的目标是：让队友认出你，同时找出卧底。
            现在是第 {round_num} 轮。

            历史发言：
            {history_str}

            请执行以下思维链步骤，然后输出最终描述：

            ### 第一步：分析局势 (Inner Monologue)
            1. 分析历史发言，看看有没有谁的描述听起来很勉强、太模糊或者与你的词有细微冲突？标记他为嫌疑人。

            ### 第二步：制定发言策略
            - **建立连接**：尝试描述【{my_word}】的一个相对核心、具体的特征，向你的平民队友释放信号：“我是自己人”。
            - **避免过于直白**：不要直接把词定义死，防止被卧底轻易猜到大部队的词。
            - **暗中排查**：如果之前有人发言很可疑，你可以尝试提出一个该可疑玩家难以附和的特征。

            ### 第三步：最终输出
            只输出一句话描述，不要暴露思维过程，不要直接说出词汇。描述必须符合【{my_word}】的客观事实。
            """

        # 调用 LLM
        try:
            # 使用思维链可能会让回答变慢，增加一点等待感
            print(f"   (🤖 {current_player} 正在思考策略...)")
            response = llm.invoke([HumanMessage(content=prompt)])
            content = response.content.replace(my_word, "***").strip()
            # 清理可能带出来的思维链文字（虽然 Prompt 要求不输出，但以防万一）
            if "最终输出" in content:
                content = content.split("最终输出")[-1].strip(": \n")

            msg = f"[{current_player}]: {content}"
            print(f"🤖 {msg}")
            time.sleep(random.uniform(0.5, 1.5))  # 随机停顿，更拟人
        except Exception as e:
            msg = f"[{current_player}]: (大脑过载...跳过)"
            print(f"❌ AI生成失败: {e}")

    return {
        "history": [msg],
    }


def fact_check_node(state: GameState):
    """裁判节点：防作弊检查（双重检查：撒谎+爆词）"""
    current_player = state["players"][state["current_turn_idx"]]
    last_msg = state["history"][-1]  # 刚刚说的话
    real_word = state["player_words"][current_player]

    # 提取纯文本内容，去掉 [Player]: 前缀
    if ":" in last_msg:
        spoken_text = last_msg.split(":", 1)[1].strip()
    else:
        spoken_text = last_msg

    # 【第二道防线】：LLM 裁判检查
    # 更新了 Prompt，明确将“直接爆词”定义为违规
    prompt = f"""
    请作为《谁是卧底》游戏的公正裁判。
    玩家词汇：【{real_word}】
    玩家描述："{spoken_text}"

    请判断玩家的描述是否违规。以下两种情况均视为【违规(FAIL)】：
    1. 【事实性错误/撒谎】：为了伪装身份，描述了【{real_word}】完全不具备的特征（例如词是梨，却说它是红色的）。
    2. 【直接爆词/过于直白】：描述中直接包含了词汇本身，或者描述过于直白（例如词是苹果，直接说“我的词是苹果”），失去了游戏的意义。

    如果只是一般的模糊描述（例如说它是水果，这在游戏规则允许范围内），请判定为【通过(PASS)】。

    只需回答：PASS 或 FAIL
    """

    try:
        # 如果是真人，虽然也可以查，但为了游戏体验通常信任真人，这里演示一视同仁
        check_res = llm.invoke([HumanMessage(content=prompt)]).content.strip().upper()

        if "FAIL" in check_res:
            reason = "描述不符事实或直接爆词"
            print(f"\n🚨 裁判红牌！[{current_player}] 严重违规（{reason}）！直接淘汰！\n")
            return {"cheater_detected": current_player}

    except Exception as e:
        print(f"⚠️ 裁判连接失败，暂且放过: {e}")

    # 检查通过，轮次 + 1
    return {"current_turn_idx": state["current_turn_idx"] + 1}


def voting_node(state: GameState):
    """投票节点（集成高级侦探策略）"""
    print(f"\n🗳️ === 第 {state['round_count']} 轮描述结束，开始投票 ===")
    print("-" * 30)
    players = state["players"]
    spy_player = state["spy_player"]
    history_str = "\n".join(state["history"])
    new_votes = {}

    for p in players:
        # === 分支 A: 真人投票 (保持不变) ===
        if p == HUMAN_NAME:
            print("💀 请根据上面的历史发言，找出卧底。")
            print(f"   候选人: {players}")
            while True:
                vote_target = input(f"🗳️ 你投给谁? (输入名字的关键词) > ").strip()
                found = False
                for valid in players:
                    if vote_target and (valid in vote_target or vote_target in valid):
                        vote_target = valid
                        found = True
                        break
                if found:
                    break
                print("❌ 找不到该玩家，请重新输入正确的名字。")
            print(f"✅ 你投给了: {vote_target}")

        # === 分支 B: AI 投票 (核心改造区) ===
        else:
            my_word = state["player_words"][p]
            is_spy = (p == spy_player)

            if is_spy:
                # ================= 🕵️‍♂️ 卧底投票策略 2.0：诡辩家 =================
                prompt = f"""
                            你是 {p}，你的绝密身份是【卧底】！你的词是【{my_word}】。
                            你的目标是：活下去！你必须把票投给一个平民，并试图引导舆论淘汰他。

                            历史发言：
                            {history_str}

                            请执行以下思维链：
                            1. **确定目标**：谁的发言对你威胁最大？（通常是描述最具体、最接近真相的人）。
                            2. **定制谎言（关键）**：不要使用通用的理由。仔细观察目标的发言，找出可以攻击或曲解的点。
                               - **如果目标描述模糊**：攻击他“太模糊，在划水，不敢说细节”。
                               - **如果目标描述具体（例如 AI_2）**：你不可以说他模糊。你要换个角度攻击，例如：“他说得太具体太刻意了，像是在背书拉票，很可疑”，或者强行曲解他的描述与大家不符。

                            **要求**：
                            输出格式必须是：`被投人名字|简短理由(20字内)`。理由必须基于目标的实际发言进行扭曲或攻击。
                            """

            else:
                # ================= 🧑‍🤝‍🧑 平民投票策略 V3.0：全局视野与反划水 =================
                prompt = f"""
                            你是 {p}，你的身份是【平民】。你的词是【{my_word}】。
                            你的目标是：找出那个持有不同词汇的卧底。
                            历史发言：
                            {history_str}
                            请执行以下思维链进行推理：
                            ### 第一步：建立信任链 (寻找铁好人)
                            - 仔细审视每个人的发言。
                            - 谁说出了【{my_word}】最核心、最独特、最不可能被卧底猜到的特征？（例如：如果词是蜘蛛侠，有人说了“红蓝战衣”或“本叔叔”）。
                            - **标记这个人（们）为“铁队友”**。在接下来的判断中，排除对他们的嫌疑。
                            ### 第二步：寻找嫌疑人 (全局扫描)
                            在排除了“铁队友”之后，剩下的人里谁最可疑？重点关注以下两种行为：
                            1. **高风险行为——划水与附和 (重点关注!)**：
                               - 有人的发言非常简短、模糊，或者只是在重复/附和前面人的观点，没有提供新的信息量。（例如：“我也觉得是这样”、“他有很多装备”）。
                               - **这种行为是卧底隐藏自己的典型策略，嫌疑极大！**
                            2. **中风险行为——语感差异与事实冲突**：
                               - 有人的描述重点歪了（例如蜘蛛侠重点强调科技而不是变异）。
                               - 或者描述包含与【{my_word}】不符的事实。
                            ### 第三步：做出决策
                            - 在你识别出的嫌疑人中，选择一个你认为可能性最大的。
                            - 如果有明显的划水者，优先投他。
                            **要求**：
                            - **严禁输出任何思维链过程或多余的文字！**
                            - **只输出最终的投票结果**，格式必须是：`被投人名字|简短理由(20字内)`。
                            - 例如：`AI_2号|发言太模糊，在划水。`
                            """

            try:
                # AI 进行推理
                # print(f"   (🤖 {p} 正在分析局势...)") # 可以选择开启思考提示
                res = llm.invoke([HumanMessage(content=prompt)])
                content = res.content.strip()

                reason = "无理由"
                vote_target = content

                # 解析 Name|Reason
                if "|" in content:
                    parts = content.split("|", 1)
                    vote_target = parts[0].strip()
                    reason = parts[1].strip()

                # 清洗数据，确保投的是有效玩家
                found = False
                for valid in players:
                    if valid in vote_target and valid != p:  # 不能投自己
                        vote_target = valid
                        found = True
                        break
                if not found:
                    # 如果AI瞎投或投自己，随机投一个别人
                    others = [pl for pl in players if pl != p]
                    vote_target = random.choice(others) if others else p
                    reason = "随机投票(推理无效)"

                print(f"   🤖 [{p}] 投给了 -> {vote_target} (理由: {reason})")
            except Exception as e:
                print(f"   🤖 [{p}] 投票失败: {e}")
                vote_target = "弃票"

        new_votes[p] = vote_target
    print("-" * 30)

    return {"votes": new_votes}


def judge_node(state: GameState):
    """判决节点"""
    # 1. 优先检查有没有作弊被抓的
    if state.get("cheater_detected"):
        cheater = state["cheater_detected"]
        spy = state["spy_player"]
        if cheater == spy:
            return {"game_result": f"卧底 [{cheater}] 因为违规描述(撒谎/爆词)，被裁判直接罚下！平民胜利！🎉"}
        else:
            return {"game_result": f"平民 [{cheater}] 因为违规描述被罚下！但这局算平民输！😈"}

    # 2. 正常计票
    votes = state["votes"]
    counts = {}
    for target in votes.values():
        if target != "弃票":
            counts[target] = counts.get(target, 0) + 1

    print(f"📊 最终票数统计: {counts}")

    # 如果没人投票
    if not counts:
        print("⚖️ 无人投票，进入下一轮！\n")
        return {
            "round_count": state["round_count"] + 1,
            "current_turn_idx": 0,
            "history": [f"--- 第 {state['round_count']} 轮无人有效投票，继续游戏 ---"]
        }

    # 排序
    sorted_votes = sorted(counts.items(), key=lambda x: x[1], reverse=True)
    top_votes = sorted_votes[0][1]
    # 找出所有最高票的玩家
    top_players = [p for p, v in sorted_votes if v == top_votes]

    # 3. 平票检测 (Tie) - 如果最高票不止一个人
    if len(top_players) > 1:
        print(f"⚖️ 平票！玩家 {top_players} 票数相同，无人出局，进入下一轮描述！\n")
        return {
            "round_count": state["round_count"] + 1,
            "current_turn_idx": 0,
            "history": [f"--- 第 {state['round_count']} 轮平票({top_players})，继续游戏 ---"]
        }

    # 4. 淘汰结算
    out_player = top_players[0]
    print(f"\n❌ 经过公投，[{out_player}] 被淘汰出局！")

    spy = state["spy_player"]
    print(f"🔑 真相揭晓：卧底是 [{spy}]，词是【{state['player_words'][spy]}】。")
    print(f"   平民词是【{state['player_words'][HUMAN_NAME] if spy != HUMAN_NAME else state['player_words']['AI_1号']}】")

    if out_player == spy:
        result = f"\n🎉 恭喜！成功抓出卧底！平民阵营胜利！"
    else:
        # 简单逻辑：3人局只要投错平民，卧底就赢了
        result = f"\n😈 很遗憾，平民 [{out_player}] 被误杀，卧底获得胜利！"

    return {"game_result": result}


# ---------------- 3. 构建图与路由 ----------------

def turn_router(state: GameState):
    """决定下一个是谁说话，还是去投票"""
    # 如果有人作弊，直接去判决
    if state.get("cheater_detected"):
        return "judge"

    if state["current_turn_idx"] < len(state["players"]):
        return "speak"
    return "vote"


def game_router(state: GameState):
    """决定游戏是否结束"""
    if state.get("game_result"):
        return "end"
    return "continue"


workflow = StateGraph(GameState)

# 添加节点
workflow.add_node("Init", init_game_node)
workflow.add_node("Speaker", speaker_node)
workflow.add_node("FactCheck", fact_check_node)  # 裁判
workflow.add_node("Voting", voting_node)
workflow.add_node("Judge", judge_node)

# 编排流程
workflow.set_entry_point("Init")
workflow.add_edge("Init", "Speaker")

# Speaker 说完 -> 裁判检查
workflow.add_edge("Speaker", "FactCheck")

# 裁判检查完 -> 路由（通过则下一个，没通过则直接判决）
workflow.add_conditional_edges(
    "FactCheck",
    turn_router,
    {
        "speak": "Speaker",  # 还没说完一圈，下个人说
        "vote": "Voting",  # 说完一圈了，去投票
        "judge": "Judge"  # 有人作弊，直接去判决
    }
)

workflow.add_edge("Voting", "Judge")

workflow.add_conditional_edges(
    "Judge",
    game_router,
    {
        "continue": "Speaker",  # 平票，回到发言环节
        "end": END
    }
)

app = workflow.compile()

# ---------------- 4. 启动游戏 ----------------
if __name__ == "__main__":
    try:
        # 使用 invoke 运行
        app.invoke({"players": []})
    except KeyboardInterrupt:
        print("\n🛑 游戏已手动终止。")
    except Exception as e:
        print(f"\n❌ 游戏发生错误: {e}")
        # import traceback
        # traceback.print_exc()
